disconnectIfNotUserがnextを呼ばずに終了しているので、socket.io内のMapにTCP socketとsessionが残り続ける
from scrapbox.ioのみ時間経過でメモリ使用量が増加する原因を、Codex CLIとともに探る
disconnectIfNotUserがnext()もnext(err)も呼ばずに終了しているので、socket.io内のMapにTCP socketとsessionが残り続けてメモリリークしているCodex CLI.icon
socket.ioの内部実装
node_modules/socket.io/dist/namespace.js:208-237 で新規ソケットを _preConnectSockets に入れたままミドルウェアのチェイン完了を待ち、完了時にのみ _doConnect() もしくはsocket._cleanup() → _preConnectSockets.delete() が呼ばれるつくりになっています。つまり next() か next(err) が必ず実行されないと、ソケットとその req/セッション一式が永遠に Map に残留します。
自前ミドルウェア
src/server/sockets/middlewares/index.js:18-27 の disconnectIfNotUser は socket.user が無いときに socket.disconnect(); socket.conn.close(); return; としており、next() も next(err) も呼ばずにチェインを打ち切っています。
この場合、前述の _preConnectSockets からエントリが除去されないので、認証に失敗した接続が発生するたびにソケットオブジェクト(および Mongoose セッション、HTTP リクエストなど)が GC されずに溜まり続けます。
socket.conn.close() は Engine.IO コネクションを閉じるだけで、Namespace._add() 側のコールバックには到達しないため socket._cleanup() も _preConnectSockets.delete() も実行されません(同ファイル node_modules/socket.io/dist/namespace.js:220-234 参照)。
結果として「時間経過に応じて」リークするというヒントどおり、クライアントの自動再接続やボットアクセスが続くほどメモリが右肩上がりになります。
対応案
認証に失敗した場合でも next(new Error("not authorized")) などで必ず next を呼び出し、Socket.IO 側にクリーンアップさせる。
もしくは socket.conn.close() 後に socket._cleanup() を明示的に呼ぶような回避策もありますが、公式に推奨されるのはミドルウェアからエラーを返す形です。
既に溜まってしまった _preConnectSockets のサイズを io._nsps.get("/")._preConnectSockets.size などでモニタリングすると、修正後に増加が止まることを確認できます。
これかshokai.icon
https://github.com/socketio/socket.io/blob/91e1c8b3584054db6072046404a24e79a17c1367/packages/socket.io/lib/namespace.ts#L146-L152
ちゃんとコード追っていったら、そもそもsocket.io 4.4.1に更新したらサーバー側からsocket切断してもクライアント側が切断されなくなったなどで実装してきた方法が間違っている事がわかってきたshokai.icon
next(err)するだけでいい
自前でのdisconnect()呼び出しは必要ない
disconnect()しても、この時点ではまだ接続が成立していない
接続が成立していないから、切断イベントが発火しなかったようだ
socket.ioの認証middlewareはただnext(err)を呼べばいい。自前でcloseする必要はないという事だ
done.icon socket.ioのdisconnectIfNotUser middlewareでEngine.IOを切断した後、next(err)を呼んでいないのでsocketとsession objectが残り続けていた #7937
非ログインユーザーがsocket.io接続してきたら
これまで
socket.io接続が成立する前にEngine.IOを切断していた
next()は呼ばない
すると、clientではconnectとdisconnectイベントが連続して発火する
修正
next(err)し、正しい手順で接続を拒否する
自前でのEngine.IOやsocket.ioの切断は不要
clientではconnectもdisconnectもどちらも発火しない
こうなる
https://scrapbox.io/files/69257a4d6b1563ce584f8703.png